#!/usr/bin/env python
#-*-coding:utf-8-*-

# "connection.py" is part of Tarinca-COA (http://code.google.com/p/taringa-api/)
# Copyright (C) - 2010 Juan B Cabral <jbc dot develop at gmail dot com>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


################################################################################
# DOCS
################################################################################

"""Clases de conexion con Taringa"""

################################################################################
# META
################################################################################

__version__ = "0.2"
__license__ = "LGPL3"
__author__ = "JBC"
__since__ = "0.2"
__date__ = "2010-02-03"


################################################################################
# IMPORTS
################################################################################

import urllib2
from datetime import datetime

try:
    import json
except ImportError:
    import simplejson as json
    
from taringa import error


################################################################################
# CONSTANTS
################################################################################

#CALL NAMES
CALL_USERS_GET_USER_DATA = "Users-GetUserData"
CALL_USERS_GET_LEVELS = "Users-GetLevels"
CALL_USERS_GET_POSTS_COMMENTS = "Users-GetPostsComments"
CALL_USERS_GET_POSTS_LIST = "Users-GetPostsList"
CALL_POSTS_GET_CATEGORIES = "Posts-GetCategories"
CALL_POSTS_GET_CLOUD_TAGS = "Posts-GetCloudTags"
CALL_SITE_GET_SITE_STATS = "Site-GetSiteStats"
CALL_POSTS_GET_TOP_POSTS = "Posts-GetTopPosts"
CALL_POSTS_GET_TOP_USERS = "Posts-GetTopUsers"
CALL_POSTS_GET_POST_LIST = "Posts-GetPostList"
CALL_POSTS_GET_POST_DATA = "Posts-GetPostData"
CALL_POSTS_GET_POST_COMMENTS = "Posts-GetPostComments"
CALL_POSTS_GET_SEARCH = "Posts-GetSearch"
CALL_POSTS_GET_POSTS_TAG = "Posts-GetPostsTag"

# TIME PARAMS
TIME_ALL_TIME = -1
TIME_TODAY = 1
TIME_YESTERDAY = 2
TIME_LAST_WEEK = 3
TIME_LAST_MONTH = 4
TIME_LAS_TWO_MONTHS = 5

# POST 
POST_TOP_MAX_SCORE = 1
POST_TOP_MAX_COMMENTS = 2
POST_TOP_MOST_FAVORITES = 3
POST_TOP_MOST_VISITED = 4

# CATEGORIES
CATEGORY_ALL = -1
CATEGORY_NEWBIE = 11
CATEGORY_FULL_USERS = -1

# USER TOP
USER_TOP_MAX_POST = 1
USER_TOP_MAX_SCORE = 2

# ORDER
ORDER_QUERY_RELEVANCE = 0
ORDER_QUERY_DATE = 1
ORDER_QUERY_SCORE = 2
ORDER_TAG_SCORE = 0
ORDER_TAG_DATE = 1
      
        
################################################################################
# TARINGA WRAPPER CLASS
################################################################################

class TaringaConnection(object):
    """Coneccion a Taringa"""
    
    def __init__(self, url, key):
        """Crea una nueva conexion a taringa.
        
        @param url: la url para conectarse
        @param key: la llave unica del desarrollador
        
        """
        if url.endswith("/"):
            self.url = url[:-1]
        else:
            self.url = url
        self.key = key.strip()
        
    def __repr__(self):
        return "<Taringa: %s {%s}>" % (self.url, self.key)
            
    def _send_request(self, call, *args):
        url = u"/".join([self.url, u"api", self.key, u"json", call])
        for a in list(args):
            if a == None:
                a = ""
            url += "/" + unicode(a).strip()
        response = {}
        try:
            json_src = urllib2.urlopen(url).read()
            if json_src:
                response = json.loads(json_src)
        except Exception as err:
            msg = str(err)
            code = 1000
            raise error.TaringaError(msg, code)
        else:
            if "error" in response:
                msg = response["error"]["msg"]
                code = response["error"]["code"]
                raise error.TaringaError(msg, code)
            return response
        
    # Llamadas relacionadas con los usuarios
    def users_get_user_data(self, nick):
        """Se lista la información sobre el usuario indicado.
        
        @attention: Hasta 20 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).

        @param nick: correspondiente del usuario a consultar.

        @return: dict
            nick: nickname del usuario.
            userlevel: rango del usuario.
            sex: sexo del usuario.
            city: ciudad de residencia del usuario.
            country: país del residencia del usuario.
            avatar: URL del avatar del usuario.
            website: sitio web del usuario.
            personal-message: mensaje personal del usuario.
            creation-date: fecha de registro de la cuenta.
            score: puntos totales del usuario.
            total-posts: posts totales creados por el usuario.
            total-comments: comentarios totales hechos por el usuario.
            trank: puesto en el T! Rank del usuario.
            * name: nombre del usuario.
            * e-mail: E-mail del usuario.
            * birthdate: fecha de nacimiento del usuario.
            * years: edad del usuario.
            * im: mensajero del usuario.
            
        @attention: Los campos indicados con '*' pueden estar en None dadas las
        configuraciones de privacidad del usuario
        """
        profile = self._send_request(CALL_USERS_GET_USER_DATA, nick)["profile"]
        profile["creation-date"] = to_date(profile["creation-date"])
        profile["score"] = int(profile["score"] or 0)
        profile["total-posts"] = int(profile["total-posts"])
        profile["total-comments"] = int(profile["total-comments"])
        # Atributos posiblemente privados
        profile["name"] = profile.get("name")
        profile["e-mail"] = profile.get("e-mail")
        profile["im"] = profile.get("im")
        profile["years"] = int(profile.get("years", 0)) or None
        if profile.get("birthdate"):
            profile["birthdate"] = to_date(profile["birthdate"] + " 0:0:0")
        else:
            profile["birthdate"] = None
        return profile
    
    def users_get_levels(self):
        """Se listan todos los rangos de los usuarios.
     
        @attention: Hasta 5 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
            
        @return: tuple of dicts
            return[i]["name"]: nombre del rango.
            return[i]["image"]: URL del ícono representativo del rango.
        
        """
        response = self._send_request(CALL_USERS_GET_LEVELS)
        return tuple(response.values())
        
    def users_get_posts_comments(self, nick):
        """Muestra los ultimos 25 comentarios en posts del usuario.
        
        @attention: Hasta 20 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded). 
        
        @param nick: correspondiente del usuario a consultar.
        
        @return: a tuple of dicts
            return[i]["comment-id"]: id del comentario
            return[i]["date"]: fecha que fue realizado el comentario.
            return[i]["post-id"]: post donde se realizo el comentario.
            return[i]["comment"]: texto del comentario.
            return[i]["url"]: url donde se encuentra el comentario
            
        """
        comments = self._send_request(CALL_USERS_GET_POSTS_COMMENTS, nick)
        if comments:
            comments = comments.values()
            for c in comments:
                c["comment-id"] = int(c["comment-id"])
                c["date"] = to_date(c["date"])
                c["post-id"] = int(c["post-id"])
        return tuple(comments)
    
    def users_get_posts_list(self, nick): 
        """Muestra los ultimos 25 posts creados por el usuario.
        
        @attention: Hasta 30 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
        
        @param nick: correspondiente del usuario a consultar.
        
        @return: a tuple of dicts
            return[i]["post-id"]: ID del post correspondiente.
            return[i]["cat-id"]: ID de la categoria del post.
            return[i]["title"]: titulo del post.
            return[i]["creation-date"]: fecha de creacion del post.
            return[i]["no-comments"]: indica si el post tiene los comentarios
                                      cerrados (True/False).
            return[i]["private"]: indica si el post es solo para usuarios
                                  registrados (True/False).
            return[i]["score"]: puntos que recibio del post.
            return[i]["url"]: URL del post.

        """
        post_list = self._send_request(CALL_USERS_GET_POSTS_LIST, nick)
        if post_list:
            post_list = post_list.values()
            for p in post_list:
                p["post-id"] = int(p["post-id"])
                p["cat-id"] = int(p["cat-id"])
                p["creation-date"] = to_date(p["creation-date"])
                p["no-comments"] = bool(int(p["no-comments"]))
                p["private"] = bool(int(p["private"]))
                p["score"] = int(p["score"] or 0)
        return tuple(post_list)

    # Llamadas relacionadas con el sitio
    def posts_get_categories(self):
        """ Se listan todas las categorías del sitio.
        
        @attention: Hasta 5 llamadas por minuto
        
        @return: a tuple of dicts
            return[i]["id"]: ID correspondiente a la categoría.
            return[i]["idname"]: nombre de la categoría en formato URL.
            return[i]["name"]: nombre de la categoría.
        
        """
        categories = self._send_request(CALL_POSTS_GET_CATEGORIES).values()
        return tuple(categories)
    
    def posts_get_cloud_tags(self):
        """Se listan los 100 tags más utilizados en todos los posts del sitio.
        
        @attention: Hasta 5 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
        
        @return: a tuple of dicts
             return[i]["position"]: posición en el ranking de tags más
                                    populares (1 para el más popular).
             return[i]["tag"]: tag en cuestión.
             return[i]["total"]: total de posts que utiliza.
        
        """
        tags = self._send_request(CALL_POSTS_GET_CLOUD_TAGS).values()
        for t in tags:
            t["total"] = int(t["total"])
        return tuple(tags)
    
    def site_get_site_stats(self):
        """ Muestra las estadísticas e información del sitio.
        
        @attention: Hasta 5 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
            
        @return: dict
            return["users-online"]: usuarios online.
            return["members"]: usuarios registrados.
            return["posts"]: posts totales.
            return["comments"]: comentarios totales.
            
        """
        stats = self._send_request(CALL_SITE_GET_SITE_STATS)
        for k, v in stats.items():
            stats[k] = int(v)
        return stats

    # Llamadas relacionadas con los TOP
    def posts_get_top_posts(self, time, post_top, post_category):
        """Se listan los 10 top posts según los criterios seleccionados.
        
        @attention: Hasta 10 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
        
        @param time:
            TIME_ALL_TIME (-1): Todos los tiempos
            TIME_TODAY (1): Hoy
            TIME_YESTERDAY (2): Ayer
            TIME_LAST_WEEK (3): Últimos 7 días
            TIME_LAST_MONTH (4): Últimos 30 días
            TIME_LAS_TWO_MONTHS (5): Últimos 60 días
    
        @param post_top:
            POST_TOP_MAX_SCORE (1): Posts con más puntos
            POST_TOP_MAX_COMMENTS (2): Posts más comentados
            POST_TOP_MOST_FAVORITES (3): Posts más guardados en favoritos
            POST_TOP_MOST_VISITED (4): Posts más visitados
    
        @param post_category:
            ID de la categoría correspondiente)
            CATEGORY_ALL (-1): Todas las categorías

        @return: a tuple of dicts
            return[i]["position"]: puesto del ranking.
            return[i]["location"]: URL del post.
            return[i]["title"]: título del post.
            return[i]["cat"]: categoría del post.
            return[i]["date"]: fecha de creación del post.
            return[i]["score"]: valor representativo del top
                                (posts/puntos/comentarios/favoritos)
            return[i]["unit"]: unidad del top (comments/posts/points/favorites)

        """
        posts = self._send_request(CALL_POSTS_GET_TOP_POSTS,
                                   time, post_top, post_category)
        if posts:
            posts = posts.values()
            for p in posts:
                p["position"] = int(p["position"]) 
                p["cat"] = int(p["cat"])
                p["date"] = to_date(p["date"])
                p["score"] = int(p["score"] or 0)
        return tuple(posts)
    
    def posts_get_top_users(self, time, post_category, user_top):
        """Se listan los 15 usuarios top según los criterios seleccionados.

        @attention: Hasta 10 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
        
        @param time:
            TIME_ALL_TIME (-1): Todos los tiempos
            TIME_TODAY (1): Hoy
            TIME_YESTERDAY (2): Ayer
            TIME_LAST_WEEK (3): Últimos 7 días
            TIME_LAST_MONTH (4): Últimos 30 días
            TIME_LAS_TWO_MONTHS (5): Últimos 60 días

        @param post_category:
            ID de la categoría correspondiente)
            CATEGORY_ALL (-1): Todas las categorías

        @param user_top:
            USER_TOP_MAX_POST (1): Usuario con más posts
            USER_TOP_MAX_SCORE (2): Usuario con más puntos
                
        @return: a tuple of dicts
            return[i]["position"]: puesto del ranking.
            return[i]["nick"]: nick del usuario.
            return[i]["score"]: valor representativo del top (posts/puntos)
            return[i]["unit"]: unidad del top (posts/points)

        """
        usrs = self._send_request(CALL_POSTS_GET_TOP_USERS,
                                  time, post_category, user_top)
        if usrs:
            usrs = usrs.values()
            for u in usrs:
                u["position"] = int(u["position"])
                u["score"] = int(u["score"] or 0)
        return tuple(usrs)

    # Llamadas relacionadas con los posts 
    def posts_get_post_list(self, category, page):
        """Obtiene la tupla de los últimos 50 posts de la categoría
        seleccionada.
        
        @attention: Hasta 10 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
        
        @param category: ID de la categoría de la cual se desean obtener
                         los últimos posts.
           CATEGORY_NEWBIE (11): Corresponde a los posts de Novatos - múltiples
                                 categorías
           CATEGORY_FULL_USERS (-1): Corresponde a todos los posts de Full
                                     Users - mutiples categorías
        
        @param page: número de página que se desea visualizar.
                    (1= primeros 50 posts, 2=del 51 al 100, etc.).
        
        @return: a tuple of dicts
            return[i]["id"]: ID del post.
            return[i]["title"]: título del post.
            return[i]["category"]: ID de la categoría del post.
            return[i]["private"]: indica si el post es privado
                                  (True = privado | False = público).
            return[i]["url"]: URL del post.

        """
        posts = self._send_request(CALL_POSTS_GET_POST_LIST, category, page)
        if posts:
            posts = posts.values()
            for p in posts:
                p["category"] = int(p["category"])
                p["private"] = bool(int(p["private"]))
                p["id"] = int(p["id"])
        return tuple(posts)
    
    def posts_get_post_data(self, id):
        """Se lista la información sobre el post indicado.
        
        @attention: Hasta 30 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).

        @param id: ID correspondiente del post a consultar.

        @return: dict
            return["title"]: título del post.
            return["cat"]: ID correspondiente a la categoría del post.
            return["date"]: fecha de creación del post.
            return["url"]: URL del post.
            return["autor"]: nick del autor del post.
            return["score"]: puntos totales del post.
            return["favorites"]: total de usuarios que lo guardaron en sus
                                 favoritos.
            return["visits"]: visitas totales del post.
            return["related"]: lista de posts relacionados.
                  return["related"][i]["id"]: ID del post relacionado.
                  return["related"][i]["title"]: título del post relacionado.
                  return["related"][i]["cat"]: ID correspondiente a la categoría
                                               del post relacionado.
                  return["related"][i]["url"]: URL del post relacionado.
            return["tags"]: lista de tags relacionados.
            return["private"]: valor para indicar si el post es privado
                              (False = público | True = privado).
            return["allow-comments"]: indica si el post acepta comentarios
                                      (False = acepta | True = no acepta).
            return["newbie"]: indica si el post corresponde a la categoría
                              Novatos (False = no es Novato | True = es Novato).
            return["sticky"]: indica si el post es un sticky
                             (False = no es sticky | True = es sticky).
            
        """
        post = self._send_request(CALL_POSTS_GET_POST_DATA, id)
        post["newbie"] = bool(int(post["newbie"]))
        post["tags"] = tuple([t.strip()
                              for t in post["tags"].split(u",") if t.strip()])
        post["visits"] = int(post["visits"])
        post["cat"] = int(post["cat"])
        post["private"] = bool(int(post["private"]))
        post["score"] = int(post["score"] or 0)
        post["allow-comments"] = bool(int(post["allow-comments"]))
        post[u"related"] = tuple(post.get("related", {}).values())
        post["favorites"] = int(post["favorites"])
        post["date"] = to_date(post["date"])
        post["sticky"] = bool(int(post["sticky"]))
        return post
    
    def posts_get_post_comments(self, id):
        """Muestra los comentarios del post deseado.
        
        @attention: Hasta 30 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
        
        @param id: ID correspondiente del post a consultar.
        
        @return: a tuple of dicts
            return[i]["comment-id"]: ID del comentario.
            return[i]["nick"]: nick del autor.
            return[i]["date"]: fecha de creación del post.
            return[i]["comment"]: texto del comentario.

        """
        cmnts = self._send_request(CALL_POSTS_GET_POST_COMMENTS, id)
        if cmnts:
            cmnts = cmnts.values()
            for c in cmnts:
                c["date"] = to_date(c["date"])
                c["comment-id"] = int(c["comment-id"])
        return tuple(cmnts)

    # Llamadas relacionadas con el buscador
    def posts_get_search(self, query, category, order, author, page):
        """Se realiza una búsqueda en todos los posts con el término elegido.
        Se pueden filtrar por categorías, autor y ordenar por puntos o fecha de
        creación.

        @attention: Hasta 30 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).

        @param query: término que se desea buscar.
        
        @param category: 
            ID de la categoría correspondiente)
            CATEGORY_ALL (-1): Todas las categorías

        @param orden: orden con el cual se desean organizar los posts.
              ORDER_QUERY_RELEVANCE (0): relevancia
              ORDER_QUERY_DATE (1): fecha
              ORDER_QUERY_SCORE (2): puntos
              
        @param autor: nick del autor por el cual se desea filtrar la búsqueda.
        
        @param página: número de página de resultados que se desean obtener
                       (50 resultados por página).
        
        @return: a tuple of dicts
            return[i]["id"]: ID del post.
            return[i]["title"]: título del post.
            return[i]["autor"]: nick del autor del post.
            return[i]["score"]: puntos del posts.
            return[i]["cat"]: ID de la categoría del post.
            return[i]["private"]: indica si el post es cerrado solo a usuarios
                                   registrado. (false = abierto, true = cerrado)
            return[i]["date"]: fecha de creación del post.
            return[i]["url"]: URL del post.
        
        """
        posts = self._send_request(CALL_POSTS_GET_SEARCH,
                                    query, category, order, author, page)
        if posts:
            posts = posts.values()
            for p in posts:
                p["private"] = bool(int(p["private"]))
                p["cat"] = int(p["cat"])
                p["score"] = int(p["score"] or 0) 
                p["date"] = to_date(p["date"]) 
                p["id"] = int(p["id"]) 
        return tuple(posts)
    
    def posts_get_posts_tag(self, tag, category, order, page):
        """Muestra los posts relacionados con el tag elegido.
        Se pueden filtrar por categorías y ordenar por puntos o fecha de
        creación.
        
        @attention: Hasta 30 llamadas por minuto o se lanza un TaringaError con
                    código 11 (Rate limiting exceded).
        
        @param tag: tag elegido para la consulta.
        
        @param category: 
            ID de la categoría correspondiente)
            CATEGORY_ALL (-1): Todas las categorías
            
        @param orden: orden con el cual se desean organizar los posts.
            ORDER_TAG_SCORE (0): puntos
            ORDER_TAG_DATE (1): fecha 
            
        @param página: número de página de resultados que se desean obtener
                       (50 resultados por página).

        @return: a tuple of dicts
            return[i]["id"]: ID del post.
            return[i]["title"]: título del post.
            return[i]["cat"]: ID de la categoría del post.
            return[i]["date"]: fecha de creación del post.
            return[i]["score"]: puntos del posts.
            return[i]["url"]: URL del post.

        """
        posts = self._send_request(CALL_POSTS_GET_POSTS_TAG,
                                   tag, category, order, page)
        if posts:
            posts = posts.values()
            for p in posts:
                p["cat"] = int(p["cat"])
                p["score"] = int(p["score"] or 0)
                p["date"] = to_date(p["date"])
                p["id"] = int()
        return tuple(posts)

    # Llamadas relacionadas con la API
    def get_rate(self, call_name):
        """Indica la cantidad de consultas disponibles para otro metodo de la
        misma instancia.
        
        En el caso de hacer la consulta antes de inciar los procesos de llamadas
        se obtiene el total de consultas disponibles para cada funcion, de esta
        se evita la necesidad de preasignar estos valores.
        
        @param call_name: El nombre de la llamada a consultar.
                          Se facilitan constantes (CALL_...) con todas las 
                          llamadas disponibles.
                    
        @return: int
        
        """
        left = self._send_request("GetRate", call_name)["left"]
        return left


################################################################################
# GLOBAL FUNCTIONS
################################################################################

def to_date(string):
    """Convierte un string que representa a una fecha en el formato usado por
    Taringa en un objeto datetime
    
    """
    return datetime.strptime(string, "%Y-%m-%d %H:%M:%S")


################################################################################
# MAIN
################################################################################

if __name__ == '__main__':
    print(__doc__)
